/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 *    GraphVisualizer.java
 *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
 *
 */
package weka.gui.graphvisualizer;

import weka.core.FastVector;
import weka.gui.ExtensionFileFilter;
import weka.gui.visualize.PrintablePanel;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.table.AbstractTableModel;

/**
 * This class displays the graph we want to visualize. It should be sufficient
 * to use only this class in weka.gui.graphvisulizer package to visualize a
 * graph. The description of a graph should be provided as a string argument
 * using readBIF or readDOT method in either XMLBIF03 or DOT format.
 * Alternatively, an InputStream in XMLBIF03 can also be provided to another
 * variation of readBIF. It would be necessary in case input is in DOT format to
 * call the layoutGraph() method to display the graph correctly after the call
 * to readDOT. It is also necessary to do so if readBIF is called and the graph
 * description doesn't have x y positions for nodes.
 * <p>
 * The graph's data is held in two FastVectors, nodes are stored as objects of
 * GraphNode class and edges as objects of GraphEdge class.
 * <p>
 * The graph is displayed by positioning and drawing each node according to its
 * x y position and then drawing all the edges coming out of it give by its
 * edges[][] array, the arrow heads are ofcourse marked in the opposite(ie
 * original direction) or both directions if the edge is reversed or is in both
 * directions. The graph is centered if it is smaller than it's display area.
 * The edges are drawn from the bottom of the current node to the top of the
 * node given by edges[][] array in GraphNode class, to avoid edges crossing
 * over other nodes. This might need to be changed if another layout engine is
 * added or the current Hierarchical engine is updated to avoid such crossings
 * over nodes.
 * 
 * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
 * @version $Revision: 7059 $
 */
public class GraphVisualizer extends JPanel implements GraphConstants,
		LayoutCompleteEventListener {

	/** for serialization */
	private static final long serialVersionUID = -2038911085935515624L;

	/** Vector containing nodes */
	protected FastVector m_nodes = new FastVector();
	/** Vector containing edges */
	protected FastVector m_edges = new FastVector();
	/** The current LayoutEngine */
	protected LayoutEngine m_le;
	/** Panel actually displaying the graph */
	protected GraphPanel m_gp;
	/** String containing graph's name */
	protected String graphID;

	/**
	 * Save button to save the current graph in DOT or XMLBIF format. The graph
	 * should be layed out again to get the original form if reloaded from
	 * command line, as the formats do not allow saving specific information for
	 * a properly layed out graph.
	 */
	protected JButton m_jBtSave;

	/** path for icons */
	private final String ICONPATH = "weka/gui/graphvisualizer/icons/";

	private FontMetrics fm = this.getFontMetrics(this.getFont());
	private double scale = 1; // current zoom
	private int nodeHeight = 2 * fm.getHeight(), nodeWidth = 24;
	private int paddedNodeWidth = 24 + 8;
	/** TextField for node's width */
	private final JTextField jTfNodeWidth = new JTextField(3);
	/** TextField for nodes height */
	private final JTextField jTfNodeHeight = new JTextField(3);
	/**
	 * Button for laying out the graph again, necessary after changing node's
	 * size or some other property of the layout engine
	 */
	private final JButton jBtLayout;
	/** used for setting appropriate node size */
	private int maxStringWidth = 0;
	/** used when using zoomIn and zoomOut buttons */
	private int[] zoomPercents = { 10, 25, 50, 75, 100, 125, 150, 175, 200,
			225, 250, 275, 300, 350, 400, 450, 500, 550, 600, 650, 700, 800,
			900, 999 };
	/** this contains the m_gp GraphPanel */
	JScrollPane m_js;

	/**
	 * Constructor<br>
	 * Sets up the gui and initializes all the other previously uninitialized
	 * variables.
	 */
	public GraphVisualizer() {
		m_gp = new GraphPanel();
		m_js = new JScrollPane(m_gp);

		// creating a new layout engine and adding this class as its listener
		// to receive layoutComplete events
		m_le = new HierarchicalBCEngine(m_nodes, m_edges, paddedNodeWidth,
				nodeHeight);
		m_le.addLayoutCompleteEventListener(this);

		m_jBtSave = new JButton();
		java.net.URL tempURL = ClassLoader.getSystemResource(ICONPATH
				+ "save.gif");
		if (tempURL != null)
			m_jBtSave.setIcon(new ImageIcon(tempURL));
		else
			System.err.println(ICONPATH
					+ Messages.getInstance().getString(
							"GraphVisualizer_Error_Text_First"));
		m_jBtSave.setToolTipText(Messages.getInstance().getString(
				"GraphVisualizer_JBtSave_SetToolTipText_Text"));
		m_jBtSave.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				JFileChooser fc = new JFileChooser(System
						.getProperty("user.dir"));
				ExtensionFileFilter ef1 = new ExtensionFileFilter(".dot",
						Messages.getInstance().getString(
								"GraphVisualizer_EF1_ExtensionFileFilter_Text"));
				ExtensionFileFilter ef2 = new ExtensionFileFilter(".xml",
						Messages.getInstance().getString(
								"GraphVisualizer_EF2_ExtensionFileFilter_Text"));
				fc.addChoosableFileFilter(ef1);
				fc.addChoosableFileFilter(ef2);
				fc.setDialogTitle(Messages.getInstance().getString(
						"GraphVisualizer_FC_SetDialogTitle_Text"));
				int rval = fc.showSaveDialog(GraphVisualizer.this);

				if (rval == JFileChooser.APPROVE_OPTION) {
					// System.out.println("Saving to file \""+
					// f.getAbsoluteFile().toString()+"\"");
					if (fc.getFileFilter() == ef2) {
						String filename = fc.getSelectedFile().toString();
						if (!filename.endsWith(".xml"))
							filename = filename.concat(".xml");
						BIFParser.writeXMLBIF03(filename, graphID, m_nodes,
								m_edges);
					} else {
						String filename = fc.getSelectedFile().toString();
						if (!filename.endsWith(".dot"))
							filename = filename.concat(".dot");
						DotParser.writeDOT(filename, graphID, m_nodes, m_edges);
					}
				}
			}
		});

		final JButton jBtZoomIn = new JButton();
		tempURL = ClassLoader.getSystemResource(ICONPATH + "zoomin.gif");
		if (tempURL != null)
			jBtZoomIn.setIcon(new ImageIcon(tempURL));
		else
			System.err.println(ICONPATH
					+ Messages.getInstance().getString(
							"GraphVisualizer_Error_Text_Second"));
		jBtZoomIn.setToolTipText(Messages.getInstance().getString(
				"GraphVisualizer_JBtZoomIn_SetToolTipText_Text"));

		final JButton jBtZoomOut = new JButton();
		tempURL = ClassLoader.getSystemResource(ICONPATH + "zoomout.gif");
		if (tempURL != null)
			jBtZoomOut.setIcon(new ImageIcon(tempURL));
		else
			System.err.println(ICONPATH
					+ Messages.getInstance().getString(
							"GraphVisualizer_Error_Text_Third"));
		jBtZoomOut.setToolTipText(Messages.getInstance().getString(
				"GraphVisualizer_JBtZoomOut_SetToolTipText_Text"));

		final JTextField jTfZoom = new JTextField("100%");
		jTfZoom.setMinimumSize(jTfZoom.getPreferredSize());
		jTfZoom.setHorizontalAlignment(JTextField.CENTER);
		jTfZoom.setToolTipText(Messages.getInstance().getString(
				"GraphVisualizer_JBtZoom_SetToolTipText_Text"));

		jTfZoom.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				JTextField jt = (JTextField) ae.getSource();
				try {
					int i = -1;
					i = jt.getText().indexOf('%');
					if (i == -1)
						i = Integer.parseInt(jt.getText());
					else
						i = Integer.parseInt(jt.getText().substring(0, i));

					if (i <= 999)
						scale = i / 100D;

					jt.setText((int) (scale * 100) + "%");

					if (scale > 0.1) {
						if (!jBtZoomOut.isEnabled())
							jBtZoomOut.setEnabled(true);
					} else
						jBtZoomOut.setEnabled(false);
					if (scale < 9.99) {
						if (!jBtZoomIn.isEnabled())
							jBtZoomIn.setEnabled(true);
					} else
						jBtZoomIn.setEnabled(false);

					setAppropriateSize();
					// m_gp.clearBuffer();
					m_gp.repaint();
					m_gp.invalidate();
					m_js.revalidate();
				} catch (NumberFormatException ne) {
					JOptionPane.showMessageDialog(
							GraphVisualizer.this.getParent(),
							Messages.getInstance()
									.getString(
											"GraphVisualizer_JOptionPaneShowMessageDialog_Text_First"),
							Messages.getInstance()
									.getString(
											"GraphVisualizer_JOptionPaneShowMessageDialog_Text_Second"),
							JOptionPane.ERROR_MESSAGE);
					jt.setText((scale * 100) + "%");
				}
			}
		});

		jBtZoomIn.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				int i = 0, s = (int) (scale * 100);
				if (s < 300)
					i = s / 25;
				else if (s < 700)
					i = 6 + s / 50;
				else
					i = 13 + s / 100;

				if (s >= 999) {
					JButton b = (JButton) ae.getSource();
					b.setEnabled(false);
					return;
				} else if (s >= 10) {
					if (i >= 22) {
						JButton b = (JButton) ae.getSource();
						b.setEnabled(false);
					}
					if (s == 10 && !jBtZoomOut.isEnabled())
						jBtZoomOut.setEnabled(true);
					// System.out.println("i: "+i+"Zoom is: "+zoomPercents[i+1]);
					jTfZoom.setText(zoomPercents[i + 1] + "%");
					scale = zoomPercents[i + 1] / 100D;
				} else {
					if (!jBtZoomOut.isEnabled())
						jBtZoomOut.setEnabled(true);
					// System.out.println("i: "+i+"Zoom is: "+zoomPercents[0]);
					jTfZoom.setText(zoomPercents[0] + "%");
					scale = zoomPercents[0] / 100D;
				}
				setAppropriateSize();
				m_gp.repaint();
				m_gp.invalidate();
				m_js.revalidate();
			}
		});

		jBtZoomOut.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				int i = 0, s = (int) (scale * 100);
				if (s < 300)
					i = (int) Math.ceil(s / 25D);
				else if (s < 700)
					i = 6 + (int) Math.ceil(s / 50D);
				else
					i = 13 + (int) Math.ceil(s / 100D);

				if (s <= 10) {
					JButton b = (JButton) ae.getSource();
					b.setEnabled(false);
				} else if (s < 999) {
					if (i <= 1) {
						JButton b = (JButton) ae.getSource();
						b.setEnabled(false);
					}
					// System.out.println("i: "+i+"Zoom is: "+zoomPercents[i-1]);
					jTfZoom.setText(zoomPercents[i - 1] + "%");
					scale = zoomPercents[i - 1] / 100D;
				} else {
					if (!jBtZoomIn.isEnabled())
						jBtZoomIn.setEnabled(true);
					// System.out.println("i: "+i+"Zoom is: "+zoomPercents[22]);
					jTfZoom.setText(zoomPercents[22] + "%");
					scale = zoomPercents[22] / 100D;
				}
				setAppropriateSize();
				m_gp.repaint();
				m_gp.invalidate();
				m_js.revalidate();
			}
		});

		// This button pops out the extra controls
		JButton jBtExtraControls = new JButton();
		tempURL = ClassLoader.getSystemResource(ICONPATH + "extra.gif");
		if (tempURL != null)
			jBtExtraControls.setIcon(new ImageIcon(tempURL));
		else
			System.err.println(ICONPATH
					+ Messages.getInstance().getString(
							"GraphVisualizer_Error_Text_Fourth"));
		jBtExtraControls.setToolTipText(Messages.getInstance().getString(
				"GraphVisualizer_JBtExtraControls_SetToolTipText_Text"));

		final JCheckBox jCbCustomNodeSize = new JCheckBox(Messages
				.getInstance().getString(
						"GraphVisualizer_JCbCustomNodeSize_JCheckBox_Text"));
		final JLabel jLbNodeWidth = new JLabel(Messages.getInstance()
				.getString("GraphVisualizer_JLbNodeWidth_JLabel_Text"));
		final JLabel jLbNodeHeight = new JLabel(Messages.getInstance()
				.getString("GraphVisualizer_JLbNodeHeight_JLabel_Text"));

		jTfNodeWidth.setHorizontalAlignment(JTextField.CENTER);
		jTfNodeWidth.setText("" + nodeWidth);
		jTfNodeHeight.setHorizontalAlignment(JTextField.CENTER);
		jTfNodeHeight.setText("" + nodeHeight);
		jLbNodeWidth.setEnabled(false);
		jTfNodeWidth.setEnabled(false);
		jLbNodeHeight.setEnabled(false);
		jTfNodeHeight.setEnabled(false);

		jCbCustomNodeSize.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				if (((JCheckBox) ae.getSource()).isSelected()) {
					jLbNodeWidth.setEnabled(true);
					jTfNodeWidth.setEnabled(true);
					jLbNodeHeight.setEnabled(true);
					jTfNodeHeight.setEnabled(true);
				} else {
					jLbNodeWidth.setEnabled(false);
					jTfNodeWidth.setEnabled(false);
					jLbNodeHeight.setEnabled(false);
					jTfNodeHeight.setEnabled(false);
					setAppropriateNodeSize();
				}
			}
		});

		jBtLayout = new JButton(Messages.getInstance().getString(
				"GraphVisualizer_JBtLayout_JButton_Text"));
		jBtLayout.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				int tmpW, tmpH;

				if (jCbCustomNodeSize.isSelected()) {
					try {
						tmpW = Integer.parseInt(jTfNodeWidth.getText());
					} catch (NumberFormatException ne) {
						JOptionPane.showMessageDialog(
								GraphVisualizer.this.getParent(),
								Messages.getInstance()
										.getString(
												"GraphVisualizer_JOptionPaneShowMessageDialog_Text_First"),
								Messages.getInstance()
										.getString(
												"GraphVisualizer_JOptionPaneShowMessageDialog_Text_Second"),
								JOptionPane.ERROR_MESSAGE);
						tmpW = nodeWidth;
						jTfNodeWidth.setText("" + nodeWidth);

					}
					try {
						tmpH = Integer.parseInt(jTfNodeHeight.getText());
					} catch (NumberFormatException ne) {
						JOptionPane.showMessageDialog(
								GraphVisualizer.this.getParent(),
								Messages.getInstance()
										.getString(
												"GraphVisualizer_JOptionPaneShowMessageDialog_Text_Third"),
								Messages.getInstance()
										.getString(
												"GraphVisualizer_JOptionPaneShowMessageDialog_Text_Fourth"),
								JOptionPane.ERROR_MESSAGE);
						tmpH = nodeHeight;
						jTfNodeWidth.setText("" + nodeHeight);
					}

					if (tmpW != nodeWidth || tmpH != nodeHeight) {
						nodeWidth = tmpW;
						paddedNodeWidth = nodeWidth + 8;
						nodeHeight = tmpH;
					}
				}
				JButton bt = (JButton) ae.getSource();
				bt.setEnabled(false);
				m_le.setNodeSize(paddedNodeWidth, nodeHeight);
				m_le.layoutGraph();
			}
		});

		GridBagConstraints gbc = new GridBagConstraints();

		final JPanel p = new JPanel(new GridBagLayout());
		gbc.gridwidth = gbc.REMAINDER;
		gbc.anchor = gbc.NORTHWEST;
		gbc.fill = gbc.NONE;
		p.add(m_le.getControlPanel(), gbc);
		gbc.gridwidth = 1;
		gbc.insets = new Insets(8, 0, 0, 0);
		gbc.anchor = gbc.NORTHWEST;
		gbc.gridwidth = gbc.REMAINDER;

		p.add(jCbCustomNodeSize, gbc);
		gbc.insets = new Insets(0, 0, 0, 0);
		gbc.gridwidth = gbc.REMAINDER;
		Container c = new Container();
		c.setLayout(new GridBagLayout());
		gbc.gridwidth = gbc.RELATIVE;
		c.add(jLbNodeWidth, gbc);
		gbc.gridwidth = gbc.REMAINDER;
		c.add(jTfNodeWidth, gbc);
		gbc.gridwidth = gbc.RELATIVE;
		c.add(jLbNodeHeight, gbc);
		gbc.gridwidth = gbc.REMAINDER;
		c.add(jTfNodeHeight, gbc);
		gbc.fill = gbc.HORIZONTAL;
		p.add(c, gbc);

		gbc.anchor = gbc.NORTHWEST;
		gbc.insets = new Insets(8, 0, 0, 0);
		gbc.fill = gbc.HORIZONTAL;
		p.add(jBtLayout, gbc);
		gbc.fill = gbc.NONE;
		p.setBorder(BorderFactory.createCompoundBorder(
				BorderFactory
						.createTitledBorder(Messages
								.getInstance()
								.getString(
										"GraphVisualizer_P_BorderFactoryCreateTitledBorder_Text")),
				BorderFactory.createEmptyBorder(4, 4, 4, 4)));
		p.setPreferredSize(new Dimension(0, 0));

		final JToolBar jTbTools = new JToolBar();
		jTbTools.setFloatable(false);
		jTbTools.setLayout(new GridBagLayout());
		gbc.anchor = gbc.NORTHWEST;
		gbc.gridwidth = gbc.REMAINDER;
		gbc.insets = new Insets(0, 0, 0, 0);
		jTbTools.add(p, gbc);
		gbc.gridwidth = 1;
		jTbTools.add(m_jBtSave, gbc);
		jTbTools.addSeparator(new Dimension(2, 2));
		jTbTools.add(jBtZoomIn, gbc);

		gbc.fill = gbc.VERTICAL;
		gbc.weighty = 1;
		JPanel p2 = new JPanel(new BorderLayout());
		p2.setPreferredSize(jTfZoom.getPreferredSize());
		p2.setMinimumSize(jTfZoom.getPreferredSize());
		p2.add(jTfZoom, BorderLayout.CENTER);
		jTbTools.add(p2, gbc);
		gbc.weighty = 0;
		gbc.fill = gbc.NONE;

		jTbTools.add(jBtZoomOut, gbc);
		jTbTools.addSeparator(new Dimension(2, 2));
		jTbTools.add(jBtExtraControls, gbc);
		jTbTools.addSeparator(new Dimension(4, 2));
		gbc.weightx = 1;
		gbc.fill = gbc.BOTH;
		jTbTools.add(m_le.getProgressBar(), gbc);

		jBtExtraControls.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				Dimension d = p.getPreferredSize();
				if (d.width == 0 || d.height == 0) {
					LayoutManager lm = p.getLayout();
					Dimension d2 = lm.preferredLayoutSize(p);
					p.setPreferredSize(d2);
					jTbTools.revalidate();
					/*
					 * // this piece of code adds in an animation // for popping
					 * out the extra controls panel Thread th = new Thread() {
					 * int h = 0, w = 0; LayoutManager lm = p.getLayout();
					 * Dimension d2 = lm.preferredLayoutSize(p);
					 * 
					 * int tow = (int)d2.getWidth(), toh = (int)d2.getHeight();
					 * //toh = (int)d2.getHeight(); //tow = (int)d2.getWidth();
					 * 
					 * public void run() { while(h<toh || w<tow) {
					 * if((h+10)<toh) h += 10; else if(h<toh) h = toh;
					 * if((w+10)<tow) w += 10; else if(w<tow) w = tow;
					 * p.setPreferredSize(new Dimension(w, h));
					 * //p.invalidate(); jTbTools.revalidate();
					 * //paint(Temp4.this.getGraphics()); try {this.sleep(30);}
					 * catch(InterruptedException ie) {ie.printStackTrace();
					 * break;} } p.setPreferredSize(new Dimension(tow,toh));
					 * jTbTools.revalidate(); } }; th.start();
					 */
				} else {
					p.setPreferredSize(new Dimension(0, 0));
					jTbTools.revalidate();
					/*
					 * Thread th = new Thread() { int h = p.getHeight(), w =
					 * p.getWidth(); LayoutManager lm = p.getLayout(); int tow =
					 * 0, toh = 0;
					 * 
					 * public void run() { while(h>toh || w>tow) {
					 * if((h-10)>toh) h -= 10; else if(h>toh) h = toh;
					 * if((w-10)>tow) w -= 10; else if(w>tow) w = tow;
					 * 
					 * p.setPreferredSize(new Dimension(w, h));
					 * //p.invalidate(); jTbTools.revalidate();
					 * //paint(Temp4.this.getGraphics()); try {this.sleep(30);}
					 * catch(InterruptedException ie) {ie.printStackTrace();
					 * break;} } p.setPreferredSize(new Dimension(tow,toh));
					 * jTbTools.revalidate(); } }; th.start();
					 */
				}
			}
		});
		this.setLayout(new BorderLayout());
		this.add(jTbTools, BorderLayout.NORTH);
		this.add(m_js, BorderLayout.CENTER);
	}

	/**
	 * This method sets the node size that is appropriate considering the
	 * maximum label size that is present. It is used internally when custom
	 * node size checkbox is unchecked.
	 */
	protected void setAppropriateNodeSize() {
		int strWidth;
		if (maxStringWidth == 0)
			for (int i = 0; i < m_nodes.size(); i++) {
				strWidth = fm
						.stringWidth(((GraphNode) m_nodes.elementAt(i)).lbl);
				if (strWidth > maxStringWidth)
					maxStringWidth = strWidth;
			}
		nodeWidth = maxStringWidth + 4;
		paddedNodeWidth = nodeWidth + 8;
		jTfNodeWidth.setText("" + nodeWidth);

		nodeHeight = 2 * fm.getHeight();
		jTfNodeHeight.setText("" + nodeHeight);
	}

	/**
	 * Sets the preferred size for m_gp GraphPanel to the minimum size that is
	 * neccessary to display the graph.
	 */
	protected void setAppropriateSize() {
		int maxX = 0, maxY = 0;

		m_gp.setScale(scale, scale);

		for (int i = 0; i < m_nodes.size(); i++) {
			GraphNode n = (GraphNode) m_nodes.elementAt(i);
			if (maxX < n.x)
				maxX = n.x;
			if (maxY < n.y)
				maxY = n.y;
		}
		// System.out.println("Scale: "+scale+" paddedWidth: "+paddedNodeWidth+
		// " nodeHeight: "+nodeHeight+"\nmaxX: "+maxX+" maxY: "+
		// maxY+" final: "+(int)((maxX+paddedNodeWidth+2)*scale)+
		// ","+(int)((maxY+nodeHeight+2)*scale) );
		m_gp.setPreferredSize(new Dimension(
				(int) ((maxX + paddedNodeWidth + 2) * scale), (int) ((maxY
						+ nodeHeight + 2) * scale)));
		// System.out.println("Size set to "+this.getPreferredSize());
	}

	/**
	 * This method is an implementation for LayoutCompleteEventListener class.
	 * It sets the size appropriate for m_gp GraphPanel and and revalidates it's
	 * container JScrollPane once a LayoutCompleteEvent is received from the
	 * LayoutEngine.
	 */
	public void layoutCompleted(LayoutCompleteEvent le) {
		setAppropriateSize();
		// m_gp.clearBuffer();
		m_gp.invalidate();
		m_js.revalidate();
		m_gp.repaint();
		jBtLayout.setEnabled(true);
	}

	/**
	 * This method lays out the graph by calling the LayoutEngine's
	 * layoutGraph() method. This method should be called to display the graph
	 * nicely, unless the input XMLBIF03 already contains some layout
	 * information (ie the x,y positions of nodes.
	 */
	public void layoutGraph() {
		if (m_le != null)
			m_le.layoutGraph();

	}

	/*********************************************************
	 * 
	 * BIF reader<br>
	 * Reads a graph description in XMLBIF03 from a string
	 * 
	 ********************************************************* 
	 */
	public void readBIF(String instring) throws BIFFormatException {
		BIFParser bp = new BIFParser(instring, m_nodes, m_edges);
		try {
			graphID = bp.parse();
		} catch (BIFFormatException bf) {
			System.out.println(Messages.getInstance().getString(
					"GraphVisualizer_ReadBIF_Error_Text_First"));
			bf.printStackTrace();
		} catch (Exception ex) {
			ex.printStackTrace();
			return;
		}

		setAppropriateNodeSize();
		if (m_le != null) {
			m_le.setNodeSize(paddedNodeWidth, nodeHeight);
		}
	} // end readBIF1

	/**
	 * 
	 * BIF reader<br>
	 * Reads a graph description in XMLBIF03 from an InputStrem
	 * 
	 * 
	 */
	public void readBIF(InputStream instream) throws BIFFormatException {
		BIFParser bp = new BIFParser(instream, m_nodes, m_edges);
		try {
			graphID = bp.parse();
		} catch (BIFFormatException bf) {
			System.out.println(Messages.getInstance().getString(
					"GraphVisualizer_ReadBIF_Error_Text_Second"));
			bf.printStackTrace();
		} catch (Exception ex) {
			ex.printStackTrace();
			return;
		}

		setAppropriateNodeSize();
		if (m_le != null) {
			m_le.setNodeSize(paddedNodeWidth, nodeHeight);
		}
		setAppropriateSize();
	} // end readBIF2

	/*********************************************************
	 * 
	 * Dot reader<br>
	 * Reads a graph description in DOT format from a string
	 * 
	 ********************************************************* 
	 */
	public void readDOT(Reader input) {
		DotParser dp = new DotParser(input, m_nodes, m_edges);
		graphID = dp.parse();

		setAppropriateNodeSize();
		if (m_le != null) {
			m_le.setNodeSize(paddedNodeWidth, nodeHeight);
			jBtLayout.setEnabled(false);
			layoutGraph();
		}
	}

	/**
	 * The panel which contains the actual graph.
	 */
	private class GraphPanel extends PrintablePanel {

		/** for serialization */
		private static final long serialVersionUID = -3562813603236753173L;

		public GraphPanel() {
			super();
			this.addMouseListener(new GraphVisualizerMouseListener());
			this.addMouseMotionListener(new GraphVisualizerMouseMotionListener());
			this.setToolTipText("");
		}

		public String getToolTipText(MouseEvent me) {
			int x, y, nx, ny;
			Rectangle r;
			GraphNode n;
			Dimension d = m_gp.getPreferredSize();
			// System.out.println("Preferred Size: "+this.getPreferredSize()+
			// " Actual Size: "+this.getSize());
			x = y = nx = ny = 0;

			if (d.width < m_gp.getWidth())
				nx = (int) ((nx + m_gp.getWidth() / 2 - d.width / 2) / scale);
			if (d.height < m_gp.getHeight())
				ny = (int) ((ny + m_gp.getHeight() / 2 - d.height / 2) / scale);

			r = new Rectangle(0, 0, (int) (paddedNodeWidth * scale),
					(int) (nodeHeight * scale));
			x += me.getX();
			y += me.getY();

			int i;
			for (i = 0; i < m_nodes.size(); i++) {
				n = (GraphNode) m_nodes.elementAt(i);
				if (n.nodeType != NORMAL)
					return null;
				r.x = (int) ((nx + n.x) * scale);
				r.y = (int) ((ny + n.y) * scale);
				if (r.contains(x, y)) {
					if (n.probs == null)
						return n.lbl;
					else
						return n.lbl
								+ Messages.getInstance().getString(
										"GraphVisualizer_GetToolTipText_Text");
				}
			}
			return null;
		}

		public void paintComponent(Graphics gr) {
			Graphics2D g = (Graphics2D) gr;
			RenderingHints rh = new RenderingHints(
					RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_ON);
			rh.put(RenderingHints.KEY_RENDERING,
					RenderingHints.VALUE_RENDER_SPEED);
			g.setRenderingHints(rh);
			g.scale(scale, scale);
			Rectangle r = g.getClipBounds();
			g.clearRect(r.x, r.y, r.width, r.height);
			// g.setColor(this.getBackground());
			// g.fillRect(0, 0, width+5, height+5);
			int x = 0, y = 0;
			Dimension d = this.getPreferredSize();
			// System.out.println("Preferred Size: "+this.getPreferredSize()+
			// " Actual Size: "+this.getSize());

			// initializing x & y to display the graph in the middle
			// if the display area is larger than the graph
			if (d.width < this.getWidth())
				x = (int) ((x + this.getWidth() / 2 - d.width / 2) / scale);
			if (d.height < this.getHeight())
				y = (int) ((y + this.getHeight() / 2 - d.height / 2) / scale);

			for (int index = 0; index < m_nodes.size(); index++) {
				GraphNode n = (GraphNode) m_nodes.elementAt(index);
				if (n.nodeType == NORMAL) {
					g.setColor(this.getBackground().darker().darker());
					g.fillOval(x + n.x + paddedNodeWidth - nodeWidth
							- (paddedNodeWidth - nodeWidth) / 2, y + n.y,
							nodeWidth, nodeHeight);

					g.setColor(Color.white);
					// g.setColor(Color.black);
					// System.out.println("drawing "+
					// ((GraphNode)m_nodes.elementAt(index)).ID+
					// " at "+" x: "+ (x+n.x+paddedNodeWidth/2-
					// fm.stringWidth( ((GraphNode)m_nodes.elementAt(index)).ID
					// )/2)+
					// " y: "+(y+n.y+nodeHeight/2+fm.getHeight()/2-2) );

					// Draw the node's label if it can fit inside the node's
					// current
					// width otherwise display its ID or otherwise just display
					// its
					// idx in the FastVector (to distinguish it from others)
					// if any can fit in node's current width
					if (fm.stringWidth(n.lbl) <= nodeWidth)
						g.drawString(
								n.lbl,
								x + n.x + paddedNodeWidth / 2
										- fm.stringWidth(n.lbl) / 2, y + n.y
										+ nodeHeight / 2 + fm.getHeight() / 2
										- 2);
					else if (fm.stringWidth(n.ID) <= nodeWidth)
						g.drawString(
								n.ID,
								x + n.x + paddedNodeWidth / 2
										- fm.stringWidth(n.ID) / 2, y + n.y
										+ nodeHeight / 2 + fm.getHeight() / 2
										- 2);
					else if (fm.stringWidth(Integer.toString(index)) <= nodeWidth)
						g.drawString(
								Integer.toString(index),
								x
										+ n.x
										+ paddedNodeWidth
										/ 2
										- fm.stringWidth(Integer
												.toString(index)) / 2, y + n.y
										+ nodeHeight / 2 + fm.getHeight() / 2
										- 2);

					g.setColor(Color.black);
				} else {
					// g.draw( new
					// java.awt.geom.QuadCurve2D.Double(n.x+paddedNodeWidth/2,
					// n.y,
					// n.x+paddedNodeWidth-nodeSize
					// -(paddedNodeWidth-nodeSize)/2,
					// n.y+nodeHeight/2,
					// n.x+paddedNodeWidth/2, n.y+nodeHeight) );
					g.drawLine(x + n.x + paddedNodeWidth / 2, y + n.y, x + n.x
							+ paddedNodeWidth / 2, y + n.y + nodeHeight);

				}

				GraphNode n2;
				int x1, y1, x2, y2;
				// System.out.println("Drawing edges of "+n.lbl);

				// Drawing all the edges coming out from the node,
				// including reversed and double ones
				if (n.edges != null)
					for (int k = 0; k < n.edges.length; k++) {
						if (n.edges[k][1] > 0) {
							n2 = (GraphNode) m_nodes.elementAt(n.edges[k][0]); // m_nodes.elementAt(k);
							// System.out.println("  -->to "+n2.lbl);
							x1 = n.x + paddedNodeWidth / 2;
							y1 = n.y + nodeHeight;
							x2 = n2.x + paddedNodeWidth / 2;
							y2 = n2.y;
							g.drawLine(x + x1, y + y1, x + x2, y + y2);
							if (n.edges[k][1] == DIRECTED) {
								if (n2.nodeType == n2.NORMAL)
									drawArrow(g, x + x1, y + y1, x + x2, y + y2);
							} else if (n.edges[k][1] == REVERSED) {
								if (n.nodeType == NORMAL)
									drawArrow(g, x + x2, y + y2, x + x1, y + y1);
							} else if (n.edges[k][1] == DOUBLE) {
								if (n.nodeType == NORMAL)
									drawArrow(g, x + x2, y + y2, x + x1, y + y1);
								if (n2.nodeType == NORMAL)
									drawArrow(g, x + x1, y + y1, x + x2, y + y2);
							}
						}
					}
			}
		}

		/**
		 * This method draws an arrow on a line from (x1,y1) to (x2,y2). The
		 * arrow head is seated on (x2,y2) and is in the direction of the line.
		 * If the arrow is needed to be drawn in the opposite direction then
		 * simply swap the order of (x1, y1) and (x2, y2) when calling this
		 * function.
		 */
		protected void drawArrow(Graphics g, int x1, int y1, int x2, int y2) {

			if (x1 == x2) {
				if (y1 < y2) {
					g.drawLine(x2, y2, x2 + 4, y2 - 8);
					g.drawLine(x2, y2, x2 - 4, y2 - 8);
				} else {
					g.drawLine(x2, y2, x2 + 4, y2 + 8);
					g.drawLine(x2, y2, x2 - 4, y2 + 8);
				}
			} else {
				// theta=line's angle from base, beta=angle of arrow's side from
				// line
				double hyp = 0, base = 0, perp = 0, theta, beta;
				int x3 = 0, y3 = 0;

				if (x2 < x1) {
					base = x1 - x2;
					hyp = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1)
							* (y2 - y1));
					theta = Math.acos(base / hyp);
				} else { // x1>x2 as we already checked x1==x2 before
					base = x1 - x2;
					hyp = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1)
							* (y2 - y1));
					theta = Math.acos(base / hyp);
				}
				beta = 30 * Math.PI / 180;
				// System.out.println("Original base "+base+" perp "+perp+" hyp "+hyp+
				// "\ntheta "+theta+" beta "+beta);

				hyp = 8;
				base = Math.cos(theta - beta) * hyp;
				perp = Math.sin(theta - beta) * hyp;

				x3 = (int) (x2 + base);
				if (y1 < y2)
					y3 = (int) (y2 - perp);
				else
					y3 = (int) (y2 + perp);

				// System.out.println("Drawing 1 from "+x2+","+y2+" to "+x3+","+y3+
				// " x1,y1 is "+x1+","+y1+" base "+base+
				// " perp "+perp+" cos(theta-beta) "+
				// Math.cos(theta-beta));
				g.drawLine(x2, y2, x3, y3);

				base = Math.cos(theta + beta) * hyp;
				perp = Math.sin(theta + beta) * hyp;

				x3 = (int) (x2 + base);
				if (y1 < y2)
					y3 = (int) (y2 - perp);
				else
					y3 = (int) (y2 + perp);
				// System.out.println("Drawing 2 from "+x2+","+y2+" to "+x3+","+y3+
				// " x1,y1 is "+x1+","+y1+" base "+base+
				// " perp "+perp);
				g.drawLine(x2, y2, x3, y3);
			}
		}

		/**
		 * This method highlights a given node and all its children and the
		 * edges coming out of it.
		 */
		public void highLight(GraphNode n) {
			Graphics2D g = (Graphics2D) this.getGraphics();
			RenderingHints rh = new RenderingHints(
					RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_ON);
			rh.put(RenderingHints.KEY_RENDERING,
					RenderingHints.VALUE_RENDER_SPEED);
			g.setRenderingHints(rh);
			g.setPaintMode();
			g.scale(scale, scale);
			int x = 0, y = 0;
			Dimension d = this.getPreferredSize();
			// System.out.println("Preferred Size: "+this.getPreferredSize()+
			// " Actual Size: "+this.getSize());

			// initializing x & y to display the graph in the middle
			// if the display area is larger than the graph
			if (d.width < this.getWidth())
				x = (int) ((x + this.getWidth() / 2 - d.width / 2) / scale);
			if (d.height < this.getHeight())
				y = (int) ((y + this.getHeight() / 2 - d.height / 2) / scale);

			// if the node is of type NORMAL only then highlight
			if (n.nodeType == NORMAL) {

				g.setXORMode(Color.green); // g.setColor(Color.green);

				g.fillOval(x + n.x + paddedNodeWidth - nodeWidth
						- (paddedNodeWidth - nodeWidth) / 2, y + n.y,
						nodeWidth, nodeHeight);
				g.setXORMode(Color.red);

				// Draw the node's label if it can fit inside the node's current
				// width otherwise display its ID or otherwise just display its
				// idx in the FastVector (to distinguish it from others)
				// if any can fit in node's current width
				if (fm.stringWidth(n.lbl) <= nodeWidth)
					g.drawString(
							n.lbl,
							x + n.x + paddedNodeWidth / 2
									- fm.stringWidth(n.lbl) / 2, y + n.y
									+ nodeHeight / 2 + fm.getHeight() / 2 - 2);
				else if (fm.stringWidth(n.ID) <= nodeWidth)
					g.drawString(
							n.ID,
							x + n.x + paddedNodeWidth / 2
									- fm.stringWidth(n.ID) / 2, y + n.y
									+ nodeHeight / 2 + fm.getHeight() / 2 - 2);
				else if (fm.stringWidth(Integer.toString(m_nodes.indexOf(n))) <= nodeWidth)
					g.drawString(
							Integer.toString(m_nodes.indexOf(n)),
							x
									+ n.x
									+ paddedNodeWidth
									/ 2
									- fm.stringWidth(Integer.toString(m_nodes
											.indexOf(n))) / 2, y + n.y
									+ nodeHeight / 2 + fm.getHeight() / 2 - 2);

				g.setXORMode(Color.green);

				GraphNode n2;
				int x1, y1, x2, y2;
				// System.out.println("Drawing edges of "+n.lbl);
				if (n.edges != null)
					// Drawing all the edges from and upward ones coming to the
					// node
					for (int k = 0; k < n.edges.length; k++) {
						if (n.edges[k][1] == DIRECTED
								|| n.edges[k][1] == DOUBLE) {
							n2 = (GraphNode) m_nodes.elementAt(n.edges[k][0]); // m_nodes.elementAt(k);
							// System.out.println("  -->to "+n2.lbl);
							x1 = n.x + paddedNodeWidth / 2;
							y1 = n.y + nodeHeight;
							x2 = n2.x + paddedNodeWidth / 2;
							y2 = n2.y;
							g.drawLine(x + x1, y + y1, x + x2, y + y2);
							if (n.edges[k][1] == DIRECTED) {
								if (n2.nodeType == n2.NORMAL) // !n2.dummy)
									drawArrow(g, x + x1, y + y1, x + x2, y + y2);
							} else if (n.edges[k][1] == DOUBLE) {
								if (n.nodeType == NORMAL) // !n.dummy)
									drawArrow(g, x + x2, y + y2, x + x1, y + y1);
								if (n2.nodeType == NORMAL) // !n2.dummy)
									drawArrow(g, x + x1, y + y1, x + x2, y + y2);
							}
							if (n2.nodeType == NORMAL)
								g.fillOval(x + n2.x + paddedNodeWidth
										- nodeWidth
										- (paddedNodeWidth - nodeWidth) / 2, y
										+ n2.y, nodeWidth, nodeHeight);

							// If n2 is not of NORMAL type
							// then carry on drawing all the edges and add all
							// the
							// dummy nodes encountered in a Vector until no
							// more dummy nodes are found and all the child
							// nodes(node n2)
							// are of type normal
							java.util.Vector t = new java.util.Vector();
							while (n2.nodeType != NORMAL || t.size() > 0) { // n2.dummy==true)
																			// {
								// System.out.println("in while processing "+n2.ID);
								if (t.size() > 0) {
									n2 = (GraphNode) t.elementAt(0);
									t.removeElementAt(0);
								}
								if (n2.nodeType != NORMAL) {
									g.drawLine(x + n2.x + paddedNodeWidth / 2,
											y + n2.y, x + n2.x
													+ paddedNodeWidth / 2, y
													+ n2.y + nodeHeight);
									x1 = n2.x + paddedNodeWidth / 2;
									y1 = n2.y + nodeHeight;
									// System.out.println("Drawing from "+n2.lbl);
									for (int m = 0; m < n2.edges.length; m++) {
										// System.out.println(" to "+n2.lbl+", "+
										// graphMatrix[tmpIndex][m]);
										if (n2.edges[m][1] > 0) {
											GraphNode n3 = (GraphNode) m_nodes
													.elementAt(n2.edges[m][0]); // m_nodes.elementAt(m);
											g.drawLine(x + x1, y + y1, x + n3.x
													+ paddedNodeWidth / 2, y
													+ n3.y);

											if (n3.nodeType == NORMAL) { // !n2.dummy)
												g.fillOval(
														x
																+ n3.x
																+ paddedNodeWidth
																- nodeWidth
																- (paddedNodeWidth - nodeWidth)
																/ 2, y + n3.y,
														nodeWidth, nodeHeight);
												drawArrow(g, x + x1, y + y1, x
														+ n3.x
														+ paddedNodeWidth / 2,
														y + n3.y);
											}
											// if(n3.nodeType!=n3.NORMAL)
											t.addElement(n3);
											// break;
										}
									}
								}
							}
						} else if (n.edges[k][1] == -REVERSED
								|| n.edges[k][1] == -DOUBLE) {
							// Drawing all the reversed and double edges which
							// are going
							// upwards in the drawing.
							n2 = (GraphNode) m_nodes.elementAt(n.edges[k][0]); // m_nodes.elementAt(k);
							// System.out.println("  -->to "+n2.lbl);
							x1 = n.x + paddedNodeWidth / 2;
							y1 = n.y;
							x2 = n2.x + paddedNodeWidth / 2;
							y2 = n2.y + nodeHeight;
							g.drawLine(x + x1, y + y1, x + x2, y + y2);

							if (n.edges[k][1] == -DOUBLE) {
								drawArrow(g, x + x2, y + y2, x + x1, y + y1);
								if (n2.nodeType != SINGULAR_DUMMY) // !n2.dummy)
									drawArrow(g, x + x1, y + y1, x + x2, y + y2);
							}

							int tmpIndex = k;
							while (n2.nodeType != NORMAL) { // n2.dummy==true) {
								g.drawLine(x + n2.x + paddedNodeWidth / 2, y
										+ n2.y + nodeHeight, x + n2.x
										+ paddedNodeWidth / 2, y + n2.y);
								x1 = n2.x + paddedNodeWidth / 2;
								y1 = n2.y;
								for (int m = 0; m < n2.edges.length; m++) {
									if (n2.edges[m][1] < 0) {
										n2 = (GraphNode) m_nodes
												.elementAt(n2.edges[m][0]); // m_nodes.elementAt(m);
										g.drawLine(x + x1, y + y1, x + n2.x
												+ paddedNodeWidth / 2, y + n2.y
												+ nodeHeight);
										tmpIndex = m;
										if (n2.nodeType != SINGULAR_DUMMY) // !n2.dummy)
											drawArrow(g, x + x1, y + y1, x
													+ n2.x + paddedNodeWidth
													/ 2, y + n2.y + nodeHeight);
										break;
									}
								}
							}
						}
					}
			}
		}
	}

	/**
	 * Table Model for the Table that shows the probability distribution for a
	 * node
	 */
	private class GraphVisualizerTableModel extends AbstractTableModel {

		/** for serialization */
		private static final long serialVersionUID = -4789813491347366596L;

		final String[] columnNames;
		final double[][] data;

		public GraphVisualizerTableModel(double[][] d, String[] c) {
			data = d;
			columnNames = c;
		}

		public int getColumnCount() {
			return columnNames.length;
		}

		public int getRowCount() {
			return data.length;
		}

		public String getColumnName(int col) {
			return columnNames[col];
		}

		public Object getValueAt(int row, int col) {
			return new Double(data[row][col]);
		}

		/*
		 * JTable uses this method to determine the default renderer/ editor for
		 * each cell.
		 */
		public Class getColumnClass(int c) {
			return getValueAt(0, c).getClass();
		}

		/*
		 * Implemented this to make sure the table is uneditable.
		 */
		public boolean isCellEditable(int row, int col) {
			return false;
		}
	}

	/**
	 * Listener class for processing mouseClicked
	 */
	private class GraphVisualizerMouseListener extends MouseAdapter {
		int x, y, nx, ny;
		Rectangle r;

		/**
		 * If the mouse is clicked on a node then this method displays a dialog
		 * box with the probability distribution table for that node IF it
		 * exists
		 */
		public void mouseClicked(MouseEvent me) {
			GraphNode n;
			Dimension d = m_gp.getPreferredSize();
			// System.out.println("Preferred Size: "+this.getPreferredSize()+
			// " Actual Size: "+this.getSize());
			x = y = nx = ny = 0;

			if (d.width < m_gp.getWidth())
				nx = (int) ((nx + m_gp.getWidth() / 2 - d.width / 2) / scale);
			if (d.height < m_gp.getHeight())
				ny = (int) ((ny + m_gp.getHeight() / 2 - d.height / 2) / scale);

			r = new Rectangle(0, 0, (int) (paddedNodeWidth * scale),
					(int) (nodeHeight * scale));
			x += me.getX();
			y += me.getY();

			int i;
			for (i = 0; i < m_nodes.size(); i++) {
				n = (GraphNode) m_nodes.elementAt(i);
				r.x = (int) ((nx + n.x) * scale);
				r.y = (int) ((ny + n.y) * scale);
				if (r.contains(x, y)) {
					if (n.probs == null)
						return;

					int noOfPrntsOutcomes = 1;
					if (n.prnts != null) {
						for (int j = 0; j < n.prnts.length; j++) {
							GraphNode n2 = (GraphNode) m_nodes
									.elementAt(n.prnts[j]);
							noOfPrntsOutcomes *= n2.outcomes.length;
						}
						if (noOfPrntsOutcomes > 511) {
							System.err
									.println(Messages
											.getInstance()
											.getString(
													"GraphVisualizer_GraphVisualizerMouseListener_MouseClicked_Error_Text_First")
											+ noOfPrntsOutcomes
											+ Messages
													.getInstance()
													.getString(
															"GraphVisualizer_GraphVisualizerMouseListener_MouseClicked_Error_Text_Second"));
							return;
						}
					}

					GraphVisualizerTableModel tm = new GraphVisualizerTableModel(
							n.probs, n.outcomes);

					JTable jTblProbs = new JTable(tm); // JTable(probabilities,
														// (Object[])n.outcomes);

					JScrollPane js = new JScrollPane(jTblProbs);

					if (n.prnts != null) {
						GridBagConstraints gbc = new GridBagConstraints();
						JPanel jPlRowHeader = new JPanel(new GridBagLayout());

						// indices of the parent nodes in the Vector
						int[] idx = new int[n.prnts.length];
						// max length of values of each parent
						int[] lengths = new int[n.prnts.length];

						// System.out.println("n.probs.length "+n.probs.length+
						// " should be "+noOfPrntsOutcomes);
						// System.out.println("n.probs[0].length "+n.probs[0].length+
						// " should be "+n.outcomes.length);
						// System.out.println("probabilities are: ");
						// for(int j=0; j<probabilities.length; j++) {
						// for(int k=0; k<probabilities[j].length; k++)
						// System.out.print(probabilities[j][k]+" ");
						// System.out.println("");
						// }

						// Adding labels for rows
						gbc.anchor = gbc.NORTHWEST;
						gbc.fill = gbc.HORIZONTAL;
						gbc.insets = new Insets(0, 1, 0, 0);
						int addNum = 0, temp = 0;
						boolean dark = false;
						while (true) {
							GraphNode n2;
							gbc.gridwidth = 1;
							for (int k = 0; k < n.prnts.length; k++) {
								n2 = (GraphNode) m_nodes.elementAt(n.prnts[k]);
								JLabel lb = new JLabel(n2.outcomes[idx[k]]);
								lb.setFont(new Font("Dialog", Font.PLAIN, 12));
								lb.setOpaque(true);
								lb.setBorder(BorderFactory.createEmptyBorder(1,
										2, 1, 1));
								lb.setHorizontalAlignment(JLabel.CENTER);
								if (dark) {
									lb.setBackground(lb.getBackground()
											.darker());
									lb.setForeground(Color.white);
								} else
									lb.setForeground(Color.black);

								temp = lb.getPreferredSize().width;
								// System.out.println("Preferred width "+temp+
								// " for "+n2.outcomes[idx[k]]);
								lb.setPreferredSize(new Dimension(temp,
										jTblProbs.getRowHeight()));
								if (lengths[k] < temp)
									lengths[k] = temp;
								temp = 0;

								if (k == n.prnts.length - 1) {
									gbc.gridwidth = gbc.REMAINDER;
									dark = (dark == true) ? false : true;
								}
								jPlRowHeader.add(lb, gbc);
								addNum++;
							}

							for (int k = n.prnts.length - 1; k >= 0; k--) {
								n2 = (GraphNode) m_nodes.elementAt(n.prnts[k]);
								if (idx[k] == n2.outcomes.length - 1 && k != 0) {
									idx[k] = 0;
									continue;
								} else {
									idx[k]++;
									break;
								}
							}

							n2 = (GraphNode) m_nodes.elementAt(n.prnts[0]);
							if (idx[0] == n2.outcomes.length) {
								JLabel lb = (JLabel) jPlRowHeader
										.getComponent(addNum - 1);
								jPlRowHeader.remove(addNum - 1);
								lb.setPreferredSize(new Dimension(lb
										.getPreferredSize().width, jTblProbs
										.getRowHeight()));
								gbc.gridwidth = gbc.REMAINDER;
								gbc.weighty = 1;
								jPlRowHeader.add(lb, gbc);
								gbc.weighty = 0;
								break;
							}
						}

						gbc.gridwidth = 1;
						// The following panel contains the names of the parents
						// and is displayed above the row names to identify
						// which value belongs to which parent
						JPanel jPlRowNames = new JPanel(new GridBagLayout());
						for (int j = 0; j < n.prnts.length; j++) {
							JLabel lb2;
							JLabel lb1 = new JLabel(
									((GraphNode) m_nodes.elementAt(n.prnts[j])).lbl);
							lb1.setBorder(BorderFactory.createEmptyBorder(1, 2,
									1, 1));
							Dimension tempd = lb1.getPreferredSize();
							// System.out.println("lengths[j]: "+lengths[j]+
							// " tempd.width: "+tempd.width);
							if (tempd.width < lengths[j]) {
								lb1.setPreferredSize(new Dimension(lengths[j],
										tempd.height));
								lb1.setHorizontalAlignment(JLabel.CENTER);
								lb1.setMinimumSize(new Dimension(lengths[j],
										tempd.height));
							} else if (tempd.width > lengths[j]) {
								lb2 = (JLabel) jPlRowHeader.getComponent(j);
								lb2.setPreferredSize(new Dimension(tempd.width,
										lb2.getPreferredSize().height));
							}
							jPlRowNames.add(lb1, gbc);
							// System.out.println("After adding "+lb1.getPreferredSize());
						}
						js.setRowHeaderView(jPlRowHeader);
						js.setCorner(JScrollPane.UPPER_LEFT_CORNER, jPlRowNames);
					}

					JDialog jd = new JDialog(
							(Frame) GraphVisualizer.this.getTopLevelAncestor(),
							Messages.getInstance().getString(
									"GraphVisualizer_Main_Jd_JDialog_Text")
									+ n.lbl, true);
					jd.setSize(500, 400);
					jd.setLocation(GraphVisualizer.this.getLocation().x
							+ GraphVisualizer.this.getWidth() / 2 - 250,
							GraphVisualizer.this.getLocation().y
									+ GraphVisualizer.this.getHeight() / 2
									- 200);

					jd.getContentPane().setLayout(new BorderLayout());
					jd.getContentPane().add(js, BorderLayout.CENTER);
					jd.setVisible(true);

					return;
				}
			}
		}

	}

	/**
	 * private class for handling mouseMoved events to highlight nodes if the
	 * the mouse is moved on one
	 */
	private class GraphVisualizerMouseMotionListener extends MouseMotionAdapter {
		int x, y, nx, ny;
		Rectangle r;
		GraphNode lastNode;

		public void mouseMoved(MouseEvent me) {
			GraphNode n;
			Dimension d = m_gp.getPreferredSize();
			// System.out.println("Preferred Size: "+this.getPreferredSize()+
			// " Actual Size: "+this.getSize());
			x = y = nx = ny = 0;

			if (d.width < m_gp.getWidth())
				nx = (int) ((nx + m_gp.getWidth() / 2 - d.width / 2) / scale);
			if (d.height < m_gp.getHeight())
				ny = (int) ((ny + m_gp.getHeight() / 2 - d.height / 2) / scale);

			r = new Rectangle(0, 0, (int) (paddedNodeWidth * scale),
					(int) (nodeHeight * scale));
			x += me.getX();
			y += me.getY();

			int i;
			for (i = 0; i < m_nodes.size(); i++) {
				n = (GraphNode) m_nodes.elementAt(i);
				r.x = (int) ((nx + n.x) * scale);
				r.y = (int) ((ny + n.y) * scale);
				if (r.contains(x, y)) {
					if (n != lastNode) {
						m_gp.highLight(n);
						if (lastNode != null)
							m_gp.highLight(lastNode);
						lastNode = n; // lastIndex = i;
					}
					break;
				}
			}
			if (i == m_nodes.size() && lastNode != null) {
				m_gp.repaint();
				// m_gp.highLight(lastNode);
				lastNode = null;
			}
		}
	}

	/**
	 * Main method to load a text file with the description of a graph from the
	 * command line
	 */
	public static void main(String[] args) {
		weka.core.logging.Logger.log(
				weka.core.logging.Logger.Level.INFO,
				Messages.getInstance().getString(
						"GraphVisualizer_Main_Logger_Text"));
		JFrame jf = new JFrame(Messages.getInstance().getString(
				"GraphVisualizer_Main_JFrame_Text"));
		GraphVisualizer g = new GraphVisualizer();

		try {
			if (args[0].endsWith(".xml")) {
				// StringBuffer sb = new StringBuffer();
				// FileReader infile = new FileReader(args[0]);
				// int i;
				// while( (i=infile.read())!=-1) {
				// sb.append((char)i);
				// }
				// System.out.println(sb.toString());
				// g.readBIF(sb.toString() );
				g.readBIF(new FileInputStream(args[0]));
			} else {
				// BufferedReader infile=new BufferedReader();
				g.readDOT(new FileReader(args[0])); // infile);
			}
		} catch (IOException ex) {
			ex.printStackTrace();
		} catch (BIFFormatException bf) {
			bf.printStackTrace();
			System.exit(-1);
		}

		jf.getContentPane().add(g);
		// RepaintManager.currentManager(jf.getRootPane()).setDoubleBufferingEnabled(false);
		jf.setDefaultCloseOperation(jf.EXIT_ON_CLOSE);
		jf.setSize(800, 600);
		// jf.pack();
		jf.setVisible(true);
	}
}
